
//
// 121119 - AH - Created.
// 130411 - AH - No longer throw exception if fails to load an object.
//

package org.japril.jutil.sol;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * <p>
 *   {@link Sol} that loads objects specified in an XML file.
 * </p>
 * @author Arne Halvorsen (AH)
 */
public final class XmlSol extends AbstractSol
{
  private static final Class<?>[] c_args0=new Class<?>[]{ String.class };
  
  private final Map<Class<?>, Constructor<?>> m_constr0=
    new HashMap<Class<?>, Constructor<?>>();
  
  private Constructor<?> getConstr0(Class<?> clazz) throws NoSuchMethodException
  {
    Constructor<?> retVal=m_constr0.get(clazz);
    if (retVal==null)
    {
      retVal=clazz.getConstructor(c_args0);
      m_constr0.put(clazz, retVal);
    }
    
    return retVal;
  }
  
  private static final class H extends DefaultHandler
  {
    private Locator m_loc=null;
    
    private final XmlSol m_sol;
    
    H(XmlSol sol){ m_sol=sol; }

    // Specified in DocumentLocator.
    public void setDocumentLocator(Locator loc){ m_loc=loc; }

    private String m_cat=null;
    
    // Specified in DocumentLocator.
    public void startElement(String uri, String lName, String qName,
      Attributes attrs) throws SAXException 
    {
      String n=qName;
      
           if (n.equals("sol")) ;
      else if (n.equals("objects")) handleObjectsStart(attrs);
      else if (n.equals("object"))  handleObjectStart(attrs);
    }
    
    private void handleObjectsStart(Attributes attrs) throws SAXException
    {
      m_cat=getMandatoryAttribute("category", attrs);
    }
    
    private void handleObjectStart(Attributes attrs) throws SAXException
    {
      String cname=getMandatoryAttribute("class", attrs);
      String arg=attrs.getValue("arg");
      
      try
      {
        Class<?> clazz=Class.forName(cname);
        Object o;
        if (arg==null)
        {
          o=clazz.newInstance();
        }
        else
        {
          Constructor<?> c=m_sol.getConstr0(clazz);
          o=(Object)c.newInstance(new Object[]{ arg });
        }
        m_sol.addLoaded(m_cat, o);
      }
      catch (ClassNotFoundException cnfx)
      {
        System.err.println("Class not found: "+cname);
      }
      catch (IllegalAccessException iax)
      {
        System.err.println("Illegal access: "+cname);
      }
      catch (InstantiationException ix)
      {
        System.err.println("Instantitation: "+cname);
      }
      catch (NoSuchMethodException nsmx)
      {
        System.err.println("No such constructor "+cname);
      }
      catch (InvocationTargetException itx)
      {
        System.err.println("Could not construct "+cname);
      }
    }
    
    private String getMandatoryAttribute(String name, Attributes attrs) 
      throws SAXException
    {
      String retVal=attrs.getValue(name);
      if (retVal==null)
      {
        throw new SAXParseException("Missing attribute "+name, m_loc);
      }
      
      return retVal;
    }
    
  }
  
  /**
   * <p>
   *   Constructor.
   * </p>
   */
  public XmlSol(){}
  
  /**
   * <p>
   *   Loads objects by parsing SOL XML.   
   * </p>
   * @param clazz {@link Class} which to use
   *              {@link Class#getResourceAsStream(String)} to get XML stream.
   * @param rp    Resource path to XML file.
   * @throws IOException If fails to read or parse.
   */
  public void parse(Class<?> clazz, String rp) throws IOException
  {
    if (rp==null)
    {
      throw new NullPointerException("rp");
    }
    
    InputStream is=null;
    try
    {
      is=clazz.getResourceAsStream(rp);
      parse(is);
    }
    finally
    {
      if (is!=null) try { is.close(); } catch (IOException _){}
    }
  }
  
  /**
   * <p>
   *   Loads objects by parsing SOL XML using {@code this} class's
   *   {@link Class#getResourceAsStream(String)} to get XML resource.
   * </p>
   * @param rp Resource path to XML file.
   * @throws IOException If fails to read or parse.
   */
  public void parse(String rp) throws IOException { parse(getClass(), rp); }
  
  /**
   * <p>
   *   Loads objects by parsing SOL XML.
   * </p>
   * @param f {@link File} with XML to parse.
   * @throws IOException If fails to read or parse.
   */
  public void parse(File f) throws IOException
  {
    if (f==null)
    {
      throw new NullPointerException("f");
    }
    
    InputStream is=null;
    try
    {
      is=new FileInputStream(f);
      parse(is);
    }
    finally
    {
      if (is!=null) try { is.close(); } catch (IOException _){}
    }
  }
  
  /**
   * <p>
   *   Loads objects by parsing SOL XML.
   * </p>
   * @param is Stream with XML to parse.
   * @throws IOException If fails to read or parse.
   */
  public void parse(InputStream is) throws IOException
  {
    if (is==null)
    {
      throw new NullPointerException("is");
    }
    
    SAXParserFactory f=SAXParserFactory.newInstance();
    f.setNamespaceAware(false);
    f.setValidating(false);
    
    try
    {
      SAXParser parser=f.newSAXParser();
      H h=new H(this);
      parser.parse(is, h);
    }
    catch (ParserConfigurationException pcx)
    {
      IOException iox=new IOException();
      iox.initCause(pcx);
      throw iox;
    }
    catch (SAXException saxx)
    {
      IOException iox=new IOException();
      iox.initCause(saxx);
      throw iox;
    }
  }
  
  public static void main(String[] args)
  {
    try
    {
      XmlSol sol=new XmlSol();
      sol.parse("soltest.xml");
      System.out.println(sol);
      
      List<String> names=sol.getLoaded("Names", String.class);
      System.out.println("Loaded names: "+names);
    }
    catch (IOException iox)
    {
      iox.printStackTrace();
      System.exit(-1);
    }
  }
  
}
